let READY_TO_START = 0
let FINISHED       = 1

const timeoutInterval = 500;
const currentToken  = @options("token");

const afterScheduleTriggered = function(lab, setting) {
    // TODO : 这里还要加个本地锁，用于解决单任务问题
    return @.async(function(){

        return lab.find("schedule_settings",setting.id);

    }).then(function(updated_setting){

        if (updated_setting.status === FINISHED || updated_setting.is_removed) {
            lab.schedule.base.@cancelSchedule(updated_setting.id);
            return @.async.resolve();
        } else {

            var scheduleID = "schedule://" + setting.id;
            var wrapper    = setting.action.split(".").reduce(function(result,name){
                if (result.po[name]) {
    
                    if (@.is(result.po[name],Function)){
                        result.functor = result.po[name];
                    } else {
                        result.po      = result.po[name];
                    }
                    
                }
    
                return result;
            },{
                po      : lab,
                functor : null
            });
            return @.async(function(){
                if (wrapper.functor){
                    // 这里应该全局依赖一个dts，来确认该任务执行是唯一的；
                    var doHandler = function (params) {
                        return @.async(function(){
                            setTimeout(()=> {
                                @.async.ensure(wrapper.functor.apply(wrapper.po,setting.action_params)).pipe(this);
                            },timeoutInterval * 2);
                        });
                    }
                    
                    if (lab.redis.client) {
                        lab.redis.client.lock(scheduleID, timeoutInterval,doHandler).pipe(this);
                    } else {
                        @.lock(scheduleID, timeoutInterval, doHandler).pipe(this);
                    }
                    
                } else {
                    this.reject("Action not found");
                }
            });
        }
    })
    
}

module.exports = function(lab) {
    
    return {
        "@listSchedules": function({ status = null, searchToken = currentToken}) {
            let condition = {};
            condition.is_removed = false;

            if (!@.is.nil(status)) {
                condition.status = status;
            }

            if (searchToken) {
                condition.token = searchToken;
            } else {
                condition.token = ["is","null"];
            }

            return lab.sqlite.find("schedule_settings",condition).then(function(result){
                this.next(result.records);
            })
        },
        "@addSchedule": function({ name, action, action_params = [], date = 0, cron = "", token }) {

            if (!date && !cron) {
                throw @lab.errors.invalid_parameters;
            }

            return lab.create("schedule_settings", {
                "id"             : @.uuid(),
                "name"           : name,
                "action"         : action,
                "action_params"  : action_params,
                "schedule_date"  : date,
                "schedule_cron"  : cron,
                "token"          : token,
                "status"         : READY_TO_START,
                "create_date"    : Date.now(),
                "last_update"    : Date.now(),
                "is_removed"     : false
            }).then(function(setting) {
                lab.schedule.plan.@startSchedule(setting).resolve(setting).pipe(this)
            })
        },
        "@updateSchedule": function(update_form) {
            return lab.find("schedule_settings", update_form.id).then(function(result) {
                ["name", "action", "action_params", "schedule_date", "schedule_cron", "token"].forEach(function(key) {
                    if (update_form[key]) {
                        result[key] = update_form[key]
                    }
                });
                result.last_update = Date.now()
                lab.update("schedule_settings", result).pipe(this)
            }).then(function(setting){
                lab.schedule.plan.@startSchedule(setting).resolve(setting).pipe(this)
            })
        },
        "@removeSchedule": function(id) {
            return lab.find("schedule_settings", id).then(function(result) {
                result.is_removed = true;
                lab.schedule.base.@cancelSchedule(result.id,result.schedule_cron ? "cron" : "schedule").then(function(){
                    lab.update("schedule_settings", result).pipe(this)
                }).pipe(this);
            })

        },
        "@startSchedules": function() {

            return lab.schedule.plan.@listSchedules({
                status   : READY_TO_START,
            }).then(function(result) {

                @.async.all(result, -1, function(setting) {
                    lab.schedule.plan.@startSchedule(setting).finished(this.next);
                }).pipe(this);
                
            })
        },
        "@createLog": function({ setting,  result = {}, error }) {

            if (error && error.message && error.message.indexOf("Lock timeout") >= 0){
                return @.async.resolve();
            }

            return lab.create("schedule_setting_logs", {
                setting_id    : setting.id,
                action        : setting.action,
                action_params : setting.action_params,
                result        : result,
                error         : error ? (error.message || error.toString()) : undefined,
                error_code    : error ? error.code : undefined,
                create_date: Date.now()
            });

        },
        "@startSchedule": function(setting) {

            if (!setting){
                throw @lab.errors.object_not_found;
            }

            if (!((@.is.nil(setting.token) && @.is.nil(currentToken)) || setting.token == currentToken)) {
                throw @lab.errors.object_not_found;
            }

            return @.async(function(){

                if (setting.schedule_date) {

                    if (setting.schedule_date < 0) {

                        return @.async(function(){
                            var superAsync = this;
                            afterScheduleTriggered(lab,setting).finished(function(error,result){
                                @.async(function(){
                                    setting.status = FINISHED;
                                    return lab.update("schedule_settings", setting);
                                }).then(function(){
                                    if (error) {
                                        superAsync.reject(error);
                                    } else {
                                        superAsync.next(result);
                                    };
                                })
                            });
                        }).resolve(setting);

                    } else if (setting.schedule_date  - timeoutInterval * 2 <= Date.now()){

                        // trigger in past
                        setting.status = FINISHED;
                        @warn("Scheduled task timeout : " + setting.id);
                        return lab.update("schedule_settings", setting);

                    } else {
                        lab.schedule.base.@addSchedule(setting.id, setting.schedule_date - timeoutInterval * 2, function(){

                            @lab(function(snapshot){
                                afterScheduleTriggered(snapshot,setting).finished(function(error,result){
                                    @.async(function(){
                                        
                                        if (error) {
                                            return snapshot.schedule.plan.@createLog({setting, error : error});
                                        } else {
                                            return snapshot.schedule.plan.@createLog({setting, result : result});
                                        }

                                    }).then(function(){
                                        setting.status = FINISHED;
                                        return snapshot.update("schedule_settings", setting);
                                    })
                                })
                            })

                        }).pipe(this);
                    }

                } else {

                    lab.schedule.base.@scheduleCron(setting.id, setting.schedule_cron, function() {

                        @lab(function(snapshot){
                            afterScheduleTriggered(snapshot,setting).finished(function(error,result){
                                if (error) {
                                    snapshot.schedule.plan.@createLog({setting, error : error});
                                } else {
                                    snapshot.schedule.plan.@createLog({setting, result : result});
                                }
                            })
                        });

                    }).pipe(this);

                }
            })
        }
    }
}